use crate::todo::MAX_KLINE_DAYS;

use super::util::angle_degrees;
use super::BacktestResult;
use super::Strategy;
use anyhow::Result;
use anyhow::anyhow;

use chrono::DateTime;
use chrono::Utc;
use fudata::db::malines::lsma::Lsma;
use fudata::db::malines::sma::Sma;
use fudata::db;
use fudata::model::kline::Klt;
use fudata::model::kline::Kline;
use fudata::model::malines::ma::LSMAv;
use fudata::model::malines::ma::MAConfig;
use fudata::model::malines::ma::MA;

use fudata::round2;

use log::info;
use serde::Deserialize;
use serde::Serialize;
/// 长时间横盘整理后突然上涨策略
///
/// 当前Kline.close与Sma5,Sma21粘合，放量
/// 并且看看是否回踩了前面的放量，即是前期放量的收盘价内当前是否反弹，
/// 考虑买入
pub struct FlatLinesBreakoutStrategy;

impl Strategy for FlatLinesBreakoutStrategy {
    async fn backtest(&self, security_id: &str) -> Result<BacktestResult> {
        println!("[{}] backtest  for moving average strategy.", security_id);
        let worker = FlatLinesBreakoutStrategyWorker::new(security_id, &Klt::M060).await;

        // This function would return some kind of result.
        // For demonstration purposes, we just return a dummy value.
        Ok(BacktestResult {
            profit: 123.0,
            win_rate: 68.0,
            avg_holding_days: 18.0,
        })
    }
}

#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct FLBPerformanceSummary {
    pub security_id: String,
    /// 当前放量Kline
    pub kline: Kline,

    /// 前次放量Kline
    pub prev_kline: Option<Kline>,

    pub prev_closev5: LSMAv,
    pub prev_closev21: LSMAv,
    pub prev_closev251: LSMAv,

    pub prev_volumev5: f64,
    pub prev_volumev21: f64,
    pub prev_volumev251: f64,

    // klines summary
}

impl FLBPerformanceSummary {
    pub fn prev_days(&self) -> i64 {
        if let Some(prev) = &self.prev_kline {
            self.kline.klts.signed_duration_since(prev.klts).num_days()
        } else {
            0
        }
    }

    pub fn volume_mav5_ratio(&self) -> f64 {
        if self.prev_volumev5 > 0.0 {
            round2((self.kline.volume as f64) / self.prev_volumev5)
        } else {
            0.0
        }
    }

    pub fn open_prevc_ratio(&self) -> f64 {
        if let Some(prev) = &self.prev_kline { round2(self.kline.open / prev.close) } else { 0.0 }
    }

    pub fn closev5_21_diff(&self) -> f64 {
        round2(((self.prev_closev5.b - self.prev_closev21.b) / self.prev_closev21.b) * 100.0)
    }
    pub fn closev5_251_diff(&self) -> f64 {
        round2(((self.prev_closev5.b - self.prev_closev251.b) / self.prev_closev251.b) * 100.0)
    }
    pub fn closev21_251_diff(&self) -> f64 {
        round2(((self.prev_closev21.b - self.prev_closev251.b) / self.prev_closev251.b) * 100.0)
    }

    pub fn closev5_degree(&self) -> f64 {
        angle_degrees(self.prev_closev5.tan)
    }

    pub fn closev21_degree(&self) -> f64 {
        angle_degrees(self.prev_closev21.tan)
    }

    pub fn closev251_degree(&self) -> f64 {
        angle_degrees(self.prev_closev251.tan)
    }

    /// Kline.close 与 lsmav5 比率
    pub fn close_mav5_ratio(&self) -> f64 {
        round2(self.kline.close / self.prev_closev5.b)
    }
    /// Kline.close 与 lsmav21 比率
    pub fn close_mav21_ratio(&self) -> f64 {
        round2(self.kline.close / self.prev_closev21.b)
    }

    /// Kline.close 与 lsmav251 比率
    pub fn close_mav251_ratio(&self) -> f64 {
        round2(self.kline.close / self.prev_closev251.b)
    }
}

pub struct FlatLinesBreakoutStrategyWorker {
    security_id: String,
    /// all day klines
    all_klines: Vec<Kline>,
    ///  imple moving average of close price
    close_ma: Lsma,
    /// volume 均值
    volume_ma: Sma,
}

impl FlatLinesBreakoutStrategyWorker {
    pub async fn new(security_id: &str, klt: &Klt) -> Result<Self> {
        let mut all_klines = db::kline::select(
            security_id,
            None,
            Some(klt),
            Some("klts DESC"),
            0,
            300 // is enough for one year
        ).await?;
        all_klines.reverse();

        if all_klines.len() < 252 {
            return Err(
                anyhow!(
                    "{} Not Enough Kline DAY data, at least 252 klines: {} ",
                    security_id,
                    all_klines.len()
                )
            );
        }

        if let Some(fstkl) = all_klines.first() {
            let close_ma: Lsma = Lsma::new(
                &(MAConfig { periods: vec![5, 21, 251] }),
                &fstkl.close
            )?;
            let volume_ma: Sma = Sma::new(
                &(MAConfig { periods: vec![5, 21, 251] }),
                &(fstkl.volume as f64)
            )?;

            return Ok(FlatLinesBreakoutStrategyWorker {
                security_id: security_id.to_string(),
                all_klines,
                close_ma,
                volume_ma,
            });
        }
        Err(anyhow!("no kline day data"))
    }

    pub async fn run_for_all(&mut self, threshold: f64) -> Option<Vec<FLBPerformanceSummary>> {
        let mut result = Vec::new();
        // 前次放量Klinne
        let mut prev_kline: Option<Kline> = None;

        let the_volume = self.all_klines[0].volume as f64;
        let mut volume_v: MA<f64> = self.volume_ma.next(&the_volume);
        let the_close = self.all_klines[0].close;
        let mut lsma_v: MA<LSMAv> = self.close_ma.next(&the_close);

        for kline in self.all_klines[1..].iter() {
            // previous volume ma
            let prev_volumev5 = volume_v.mav(5).unwrap_or_else(|| 1.0);
            let prev_volumev21 = volume_v.mav(21).unwrap_or_else(|| 1.0);
            let prev_volumev251 = volume_v.mav(251).unwrap_or_else(|| 1.0);

            // previous close ma

            let prev_closev5 = lsma_v.mav(5).unwrap_or_else(|| LSMAv {
                tan: 0.0,
                b: 0.0,
            });
            let prev_closev21 = lsma_v.mav(21).unwrap_or_else(|| LSMAv {
                tan: 0.0,
                b: 0.0,
            });
            let prev_closev251 = lsma_v.mav(251).unwrap_or_else(|| LSMAv {
                tan: 0.0,
                b: 0.0,
            });

            info!(
                "{}: {} / {} > {threshold} = {}",
                kline.id_date(),
                kline.volume as f64,
                prev_volumev5,
                (kline.volume as f64) / prev_volumev5 > threshold
            );
            if (kline.volume as f64) / prev_volumev5 > threshold {
                result.push(FLBPerformanceSummary {
                    security_id: self.security_id.clone(),
                    kline: kline.clone(),
                    prev_kline,
                    prev_closev5,
                    prev_closev21,
                    prev_closev251,
                    prev_volumev5,
                    prev_volumev21,
                    prev_volumev251,
                });
                prev_kline = Some(kline.clone());
            }

            // refresh ma computer
            let the_volume = kline.volume as f64;
            volume_v = self.volume_ma.next(&the_volume);
            let the_close = kline.close;
            lsma_v = self.close_ma.next(&the_close);
        }
        if result.len() > 0 {
            Some(result)
        } else {
            None
        }
    }

    pub async fn run_for_date(
        &mut self,
        kline_id_date: &str,
        threshold: f64
    ) -> Option<FLBPerformanceSummary> {
        if
            let Some(end_kline) = self.all_klines
                .iter()
                .find(|kline| { kline.id_date().eq_ignore_ascii_case(kline_id_date) })
        {
            // 前次放量Klinne
            let mut prev_kline: Option<Kline> = None;

            let the_volume = self.all_klines[0].volume as f64;
            let mut volume_v: MA<f64> = self.volume_ma.next(&the_volume);
            let the_close = self.all_klines[0].close;
            let mut lsma_v: MA<LSMAv> = self.close_ma.next(&the_close);

            for kline in self.all_klines[1..].iter() {
                if kline.klts <= end_kline.klts {
                    // previous volume ma
                    let prev_volumev5 = volume_v.mav(5).unwrap_or_else(|| 1.0);
                    let prev_volumev21 = volume_v.mav(21).unwrap_or_else(|| 1.0);
                    let prev_volumev251 = volume_v.mav(251).unwrap_or_else(|| 1.0);

                    // previous close ma

                    let prev_closev5 = lsma_v.mav(5).unwrap_or_else(|| LSMAv {
                        tan: 0.0,
                        b: 0.0,
                    });
                    let prev_closev21 = lsma_v.mav(21).unwrap_or_else(|| LSMAv {
                        tan: 0.0,
                        b: 0.0,
                    });
                    let prev_closev251 = lsma_v.mav(251).unwrap_or_else(|| LSMAv {
                        tan: 0.0,
                        b: 0.0,
                    });

                    if
                        kline.klts == end_kline.klts &&
                        (kline.volume as f64) / prev_volumev5 > threshold
                    {
                        return Some(FLBPerformanceSummary {
                            security_id: self.security_id.clone(),
                            kline: kline.clone(),
                            prev_kline,
                            prev_closev5,
                            prev_closev21,
                            prev_closev251,
                            prev_volumev5,
                            prev_volumev21,
                            prev_volumev251,
                        });
                    }

                    // refresh prev_kline
                    if (kline.volume as f64) / prev_volumev5 > threshold {
                        prev_kline = Some(kline.clone());
                    }

                    // refresh ma computer
                    let the_volume = kline.volume as f64;
                    volume_v = self.volume_ma.next(&the_volume);
                    let the_close = kline.close;
                    lsma_v = self.close_ma.next(&the_close);
                }
            }
        }
        None
    }
}

#[cfg(test)]
mod test {
    use chrono::Duration;
    use chrono::Utc;
    use fudata::db;
    use fudata::model::kline::Klt;
    use fudata::model::security;
    use fudata::model::security::Market;
    use fudata::model::security::Security;
    use fudata::round2;
    use log::info;
    use surrealdb::sql::Thing;
    use crate::qtrade::strategy::flat_lines_breakout::FlatLinesBreakoutStrategyWorker;

    use super::FLBPerformanceSummary;

    #[tokio::test]
    async fn test_run_for_date() {
        let _ = db::init().await.unwrap();

        pretty_env_logger::init();

        let securities: Vec<Security>;
        if 1 == 1 {
            let all_securities = db::security
                ::select(
                    &Some(Market::SH),
                    Some(security::SecurityType::STK),
                    None,
                    Some("id ASC"),
                    0,
                    3000 //MAX_STK_SECURITY
                ).await
                .unwrap();
            // 使用迭代器过滤出 security_id 以 "SH.SZ.60" 开头的对象,如果您想要拥有所有权而非引用：
            securities = all_securities
                .into_iter()
                .filter(|security| {
                    let security_id = security.id_raw();
                    security_id.starts_with("SH.STK.60") ||
                        security_id.starts_with("SZ.STK.30") ||
                        security_id.starts_with("SZ.STK.00")
                })
                .collect();
        } else {
            securities = vec![
                Security {
                    id: Some(Thing::from(("security", "SZ.STK.300363"))),
                    ..Default::default()
                },
                Security {
                    id: Some(Thing::from(("security", "SZ.STK.002648"))),
                    ..Default::default()
                },
                Security {
                    id: Some(Thing::from(("security", "SH.STK.605580"))),
                    ..Default::default()
                }
            ];
        }

        let mut all_flb_perfs = Vec::new();
        let the_date = Utc::now() - Duration::days(1);
        let kline_id_date = the_date.format("%Y%m%d").to_string();
        for (idx, security) in securities.iter().enumerate() {
            let security_id = security.id_raw();

            info!("****[{idx}/{}]{} {}", securities.len(), security_id, kline_id_date);

            let rs = FlatLinesBreakoutStrategyWorker::new(security_id.as_str(), &Klt::DAY).await;

            match rs {
                Ok(mut worker) => {
                    let perf = worker.run_for_date(&kline_id_date, 2.0).await;
                    if let Some(v) = perf {
                        all_flb_perfs.push(v);
                    }
                }

                Err(e) => {
                    println!("err={:?}", e);
                }
            }
        }

        all_flb_perfs.sort_by(|a, b|
            b
                .volume_mav5_ratio()
                .partial_cmp(&a.volume_mav5_ratio())
                .unwrap_or(std::cmp::Ordering::Equal)
        );

        print_perfs(&all_flb_perfs);
    }

    fn print_perfs(all_flb_perfs: &Vec<FLBPerformanceSummary>) {
        println!(
            "#, security_id, id_date,prev_days, volume_mav5, open_prevc_ratio,  closev5_21df, close5deg,21deg,251deg, closev5r, closev21r,closev251r,251tan, 251b, score"
        );
        for (idx, perf) in all_flb_perfs.iter().enumerate() {
            let prev_date = if let Some(prev) = &perf.prev_kline {
                prev.id_date()
            } else {
                "-".to_owned()
            };

            println!(
                "{idx},{},{}-{},{},{},{},{},{},{},{},{},{},{},{},{},{}",
                perf.security_id,
                prev_date,
                perf.kline.id_date(),
                perf.prev_days(),
                perf.volume_mav5_ratio(),
                perf.open_prevc_ratio(),
                perf.closev5_21_diff(),
                perf.closev5_degree(),
                perf.closev21_degree(),
                perf.closev251_degree(),
                perf.close_mav5_ratio(),
                perf.close_mav21_ratio(),
                perf.close_mav251_ratio(),
                perf.prev_closev251.tan,
                perf.prev_closev251.b,

                round2(
                    perf.closev5_21_diff() +
                        perf.closev5_degree() +
                        perf.closev21_degree() +
                        perf.closev251_degree() +
                        perf.close_mav5_ratio() +
                        perf.close_mav21_ratio() +
                        perf.close_mav251_ratio()
                )
            );
        }
    }

    #[tokio::test]
    async fn test_run_for_all() {
        let _ = db::init().await.unwrap();

        pretty_env_logger::init();

        let security_id = "SZ.STK.000062";

        let mut all_flb_perfs = Vec::new();

        let rs = FlatLinesBreakoutStrategyWorker::new(security_id, &Klt::DAY).await;

        match rs {
            Ok(mut worker) => {
                let perf = worker.run_for_all(2.0).await;
                if let Some(v) = perf {
                    all_flb_perfs.extend(v);
                }
            }

            Err(e) => {
                println!("err={:?}", e);
            }
        }

        print_perfs(&all_flb_perfs);
    }

    //
}
